// 柯里化（Currying）是一种技术，
// 它把接受m个参数的函数变成接受n个参数的函数（0<n<m），
// 并且该函数返回一个新函数，这个新函数接受余下的参数……如此循环，直到最后返回结果。
// 柯理化函数的核心是闭包，因为闭包，所以内层函数才能够保留父作用域中的变量。

// 举例：

// 一般计算add
function add(x, y) {
  return x + y;
}

// 柯理化计算add 把add的 x,y 两个参数编程了用一个函数接受x 然后返回一个函数去 处理y
function curyadd(x) {
  return function (y) {
    return x + y;
  };
}

console.log(add(1, 2)); // 3
console.log(curyadd(1)); // Function
console.log(curyadd(1)(2)); // 3

// 叠加创建一个通用的柯理化函数
function currying(fun) {
  var args = Array.prototype.slice.call(arguments, 1); //将除了第一个fn变量，将其他参数变量都赋值在args变量中
  return function () {
    var _args = args.concat(Array.prototype.slice.call(arguments)); // 合并在返回函数的变量参
    return fun.apply(null, _args); // 参数给返回函数使用。
  };
}

// 这里是另外一个面试题
// 实现方法
// fn(1) //1
// fn(1,2) //5
// fn(1)(2)(3) //14

function curyadd_1() {
  // 第一次执行时，定义一个数组专门用来存储所有的参数
  var _args = Array.prototype.slice.call(arguments);
  // 在内部声明一个函数，利用闭包的特性保存_args并收集所有的参数值
  var _adder = function () {
    _args.push(...arguments);
    return _adder;
  };

  // 利用toString隐式转换的特性，当最后执行时隐式转换，并计算最终的值返回
  _adder.toString = function () {
    return _args.reduce(function (a, b = 0) {
      return a + b * b;
    });
  };
  _adder.value = _adder;
  return _adder;
}

// 或者使用箭头函数 ！！注意~~~~,箭头函数没有this 和 arguments
curyadd_narrow = (...arguments) => {
  // 第一次执行时，定义一个数组专门用来存储所有的参数
  const _args = Array.prototype.slice.call(arguments);
  // 在内部声明一个函数，arguments 作为参数传递值
  const _adder = (...arguments) => {
    _args.push(...arguments);
    return _adder;
  };

  // 利用toString隐式转换的特性，当最后执行时隐式转换，并计算最终的值返回
  _adder.toString = () => _args.reduce((a, b = 0) => a + b * b);
  return _adder;
};

const ad1 = curyadd_narrow(1).toString();
const ad2 = curyadd_narrow(1, 2).toString();
const ad3 = curyadd_narrow(1)(2)(3).toString();
console.log({ ad1, ad2, ad3 });

// 为什么要函数柯里化
// 一、参数复用

// 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
  return reg.test(txt);
}

check(/\d+/g, "test"); //false
check(/[a-z]+/g, "test"); //true

// Currying后
function curryingCheck(reg) {
  return function (txt) {
    return reg.test(txt);
  };
}

var hasNumber = curryingCheck(/\d+/g);
var hasLetter = curryingCheck(/[a-z]+/g);

hasNumber("test1"); // true
hasNumber("testtest"); // false
hasLetter("21212"); // false
// 二、提前确认
var on = function (element, event, handler) {
  if (document.addEventListener) {
    if (element && event && handler) {
      element.addEventListener(event, handler, false);
    }
  } else {
    if (element && event && handler) {
      element.attachEvent("on" + event, handler);
    }
  }
};

var on = (function () {
  if (document.addEventListener) {
    return function (element, event, handler) {
      if (element && event && handler) {
        element.addEventListener(event, handler, false);
      }
    };
  } else {
    return function (element, event, handler) {
      if (element && event && handler) {
        element.attachEvent("on" + event, handler);
      }
    };
  }
})();
// 换一种写法可能比较好理解一点，上面就是把isSupport这个参数给先确定下来了
var on = function (isSupport, element, event, handler) {
  isSupport = isSupport || document.addEventListener;
  if (isSupport) {
    return element.addEventListener(event, handler, false);
  } else {
    return element.attachEvent("on" + event, handler);
  }
};

// 三、延迟计算/运行
// 像我们js中经常使用的bind，实现的机制就是Currying.
Function.prototype.bind = function (context) {
  var _this = this;
  var args = Array.prototype.slice.call(arguments, 1);

  return function () {
    return _this.apply(context, args);
  };
};
